﻿
// 2020/1/13: 首个记录（基于TrafficSampleV1.cs 2019/12/17版本修改）
// 2020/1/15: 支持插值
// 2020/3/9: 增加红灯/绿灯闪烁状态
// 2020/6/24: 实现ToGeneralSample
// 2020/6/28: 修正ToGeneralSample
// 2020/7/20: 更新样本标题
// 2020/11/2: 增加停车位是否被占用字段
// 2020/11/6: 修正插值函数
// 2021/4/6: 基于ASEvaAPI 2.0
// 2021/6/11: 实现ToGeneralSample(channel)
// 2022/10/18: 增加SO标签
// 2022/11/3: 使用SetTime复制全部时间信息
// 2023/9/12: StopSign改为TrafficSign，并增加TrafficSignType
// 2023/10/11: 补充部分英文注释
// 2024/5/20: 新增TrafficLight.StopPosX/Y

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ASEva;
using SO = System.Reflection.ObfuscationAttribute;

namespace TrafficSampleV2
{
    [SO]
    enum TrafficElementType
    {
        Invalid = 0,
        SpeedLimit = 1, // 限速标志
        TrafficSign = 2, // 交通标志
        TrafficLight = 3, // 交通信号灯
        StopLine = 4, // 停止线
        SpeedBump = 5, // 减速带
        ArrowMark = 6, // 路面箭头
        ProhibitionZone = 7, // 禁停区
        ParkingSlot = 8, // 停车位
        ZebraCrossing = 9, // 斑马线
    }

    [SO]
	enum TrafficSignType
	{
		Stop = 0, // 停止标志

		NoEntry = 1, // 禁止驶入
		NoDriving = 2, // 禁止通行
		NoParking = 3, // 禁止停车
		NoLongTimeParking = 4, // 禁止长时间停车
		NoVehicle = 5, // 禁止机动车驶入
		NoTruck = 6, // 禁止货车驶入
		NoBus = 7, // 禁止客车驶入
		NoPedestrian = 8, // 禁止行人进入
		NoLeftTurn = 9, // 禁止左转
		NoRightTurn = 10, // 禁止右转
		NoAnyTurn = 11, // 禁止转弯
		NoForward = 12, // 禁止直行
		NoForwardOrLeft = 13, // 禁止直行或左转
		NoForwardOrRight = 14, // 禁止直行或右转
		NoUTurn = 15, // 禁止调头
		NoHorn = 16, // 禁止鸣笛
		NoOvertake = 17, // 禁止超车
		NoOvertakeEnder = 18, // 禁止超车区间结束
		NoDangerousGoods = 19, // 禁止危险品车辆驶入

		LowSpeedLimit = 50, // 低速限速
		SpeedLimitEnder = 51, // 限速区间结束
		WeightLimitWheel = 52, // 轴重限重
		WeightLimitTotal = 53, // 全车限重
		HeightLimit = 54, // 高度限制
		WidthLimit = 55, // 宽度限制
		Yield = 56, // 让行标志
		StopAndCheck = 57, // 停车检查

		GeneralSign = 100, // 指示类标志
		GeneralNoSpecialVehicle = 101, // 禁止特种车辆驶入
		GeneralConstruction = 102, // 施工标志
		GeneralWarning = 103, // 警告牌
		GeneralGreenBackground = 104, // 绿底指示牌
		GeneralBlueBackground = 105, // 蓝底指示牌
		GeneralRedBackground = 106, // 红底指示牌
		GeneralWhiteBackground = 107, // 白底指示牌
		GeneralYellowRoadID = 108, // 黄底道路ID牌
		GeneralRedRoadID = 109, // 红底道路ID牌
	};

    class ArrowCombination
    {
        public bool GoStraight { get; set; } // 直行
        public bool TurnLeft { get; set; } // 左转
        public bool TurnRight { get; set; } // 右转
        public bool UTurn { get; set; } // 调头
    };

    class TrafficElement // Base class of traffic element / 交通元素基类
    {
        public TrafficElementType Type { get; set; } // 交通元素类别
        public int ID { get; set; } // 0 as invalid / 交通元素ID，0表示无效
        public int? RawID { get; set; } // 交通元素的原始ID
    }

    class SpeedLimit : TrafficElement
    {
        public uint LimitedSpeed { get; set; } // [KPH] 限速
        public double? PosX { get; set; } // [m] 纵向位置
        public double? PosY { get; set; } // [m] 横向位置
        public double? PosZ { get; set; } // [m] 垂直位置

        public SpeedLimit()
        {
            Type = TrafficElementType.SpeedLimit;
        }
    }

    class TrafficSign : TrafficElement
    {
        public TrafficSignType SignType { get; set; } // 交通标志类别
        public double? PosX { get; set; } // [m] 纵向位置
        public double? PosY { get; set; } // [m] 横向位置
        public double? PosZ { get; set; } // [m] 垂直位置

        public TrafficSign()
        {
            Type = TrafficElementType.TrafficSign;
        }
    }

    [SO]
    enum TrafficLightState
    {
        Invalid = 0,
        Stop = 1, // 红灯
        Continue = 2, // 黄灯
        Caution = 3, // 黄灯闪烁
        Allow = 4, // 绿灯
        AllowFlash = 5, // 绿灯闪烁
        StopFlash = 6, // 红灯闪烁
    }

    class TrafficLight : TrafficElement
    {
        public TrafficLightState State { get; set; } // 信号灯状态
        public ArrowCombination Arrows { get; set; } // null while direction is not specified / 信号灯的箭头组合，若不分方向(实心圆信号)则为空
        public double? PosX { get; set; } // [m] 纵向位置
        public double? PosY { get; set; } // [m] 横向位置
        public double? PosZ { get; set; } // [m] 垂直位置
        public double? StopPosX { get; set; } // [m] 纵向停止位置
        public double? StopPosY { get; set; } // [m] 横向停止位置

        public TrafficLight()
        {
            Type = TrafficElementType.TrafficLight;
        }
    }

    class StopLine : TrafficElement
    {
        public double Distance { get; set; } // [m] 停止线与x轴相交位置
        public double? Heading { get; set; } // [°] 停止线法线朝向，逆时针为正方向

        public StopLine()
        {
            Type = TrafficElementType.StopLine;
        }
    }

    class SpeedBump : TrafficElement
    {
        public double Distance { get; set; } // [m] 减速带与x轴相交位置
        public double? Heading { get; set; } // [°] 减速带法线朝向，逆时针为正方向

        public SpeedBump()
        {
            Type = TrafficElementType.SpeedBump;
        }
    }

    class ArrowMark : TrafficElement
    {
        public ArrowCombination Arrows { get; set; } // 路面标志的箭头组合
        public double? PosX { get; set; } // [m] 纵向位置
        public double? PosY { get; set; } // [m] 横向位置

        public ArrowMark()
        {
            Type = TrafficElementType.ArrowMark;
            Arrows = new ArrowCombination();
        }
    }

    class ProhibitionZone : TrafficElement
    {
        public double CenterX { get; set; } // [m] 禁停区中央的纵向位置
        public double CenterY { get; set; } // [m] 禁停区中央的横向位置
        public double Width { get; set; } // [m] 禁停区的宽度
        public double Length { get; set; } // [m] 禁停区的长度
        public double? Heading { get; set; } // [°] 禁停区的朝向(沿长度方向)，逆时针为正方向
        
        public ProhibitionZone()
        {
            Type = TrafficElementType.ProhibitionZone;
        }
    }

    [SO]
    enum ParkingSlotOccupancy
    {
        Unknown = 0, // 未知
        NotOccupied = 1, // 未被占用
        Occupied = 2, // 被占用
    }

    class ParkingSlot : TrafficElement
    {
        public double CenterX { get; set; } // [m] 停车位中央的纵向位置
        public double CenterY { get; set; } // [m] 停车位中央的横向位置
        public double Width { get; set; } // [m] 停车位的宽度
        public double Length { get; set; } // [m] 停车位的长度
        public double? Heading { get; set; } // [°] 停车位的朝向(沿长度方向)，逆时针为正方向
        public ParkingSlotOccupancy Occupancy { get; set; } // 停车位是否被占用

        public ParkingSlot()
        {
            Type = TrafficElementType.ParkingSlot;
        }
    }

    class ZebraCrossing : TrafficElement
    {
        public double NearDistance { get; set; } // [m] 斑马线近端与x轴相交位置
        public double? FarDistance { get; set; } // [m] 斑马线远端与x轴相交位置
        public double? Heading { get; set; } // [°] 斑马线的朝向，逆时针为正方向

        public ZebraCrossing()
        {
            Type = TrafficElementType.ZebraCrossing;
        }
    }

    class TrafficSample : Sample
    {
        public List<TrafficElement> Elements { get; set; } // List of traffic elements / 交通元素列表

        public TrafficSample()
        {
            Elements = new List<TrafficElement>();
        }

        public static string Title
        {
            get
            {
                return "Traffic element count,First element's data's offset,First element's data's size,First element's type,First element's ID,First element's raw ID,Reserved,Second element's type / First data value,etc.";
            }
        }

        public static string Protocol
        {
            get
            {
                return "traffic-sample-v2";
            }
        }

        public static string[] Protocols
        {
            get
            {
                return new string[] { "traffic-sample-v1", "traffic-sample-v2" };
            }
        }

        public override string GetGeneralSampleProtocol()
        {
            return Protocol;
        }


        public override string[] GetGeneralSampleProtocols()
        {
            return Protocols;
        }

        public override GeneralSample ToGeneralSample(int channel)
        {
            var sample = new GeneralSample();
            sample.SetTime(this);
            sample.Protocol = Protocol;
            sample.Channel = channel;

            var offsets = new int[Elements.Count];
            var lengthes = new int[Elements.Count];
            int totalDataSize = 0;
            for (int i = 0; i < Elements.Count; i++)
            {
                int len = 0;
                switch (Elements[i].Type)
                {
                    case TrafficElementType.SpeedLimit:
                        len = 6;
                        break;
                    case TrafficElementType.TrafficSign:
                        len = 5;
                        break;
                    case TrafficElementType.TrafficLight:
                        len = 10;
                        break;
                    case TrafficElementType.StopLine:
                        len = 4;
                        break;
                    case TrafficElementType.SpeedBump:
                        len = 4;
                        break;
                    case TrafficElementType.ArrowMark:
                        len = 8;
                        break;
                    case TrafficElementType.ProhibitionZone:
                        len = 7;
                        break;
                    case TrafficElementType.ParkingSlot:
                        len = 7;
                        break;
                    case TrafficElementType.ZebraCrossing:
                        len = 5;
                        break;
                    default:
                        break;
                }
                offsets[i] = totalDataSize;
                lengthes[i] = len;
                totalDataSize += len;
            }

            var data = new GeneralSampleValue[1 + Elements.Count * 6 + totalDataSize];
            int dataBase = 1 + Elements.Count * 6;

            data[0] = new GeneralSampleValue((double)Elements.Count);
            for (int i = 0; i < Elements.Count; i++)
            {
                var elem = Elements[i];
                data[1 + 6 * i] = new GeneralSampleValue((double)offsets[i]);
                data[2 + 6 * i] = new GeneralSampleValue((double)lengthes[i]);
                data[3 + 6 * i] = new GeneralSampleValue((double)(uint)elem.Type);
                data[4 + 6 * i] = new GeneralSampleValue((double)elem.ID);
                if (elem.RawID != null) data[5 + 6 * i] = new GeneralSampleValue((double)elem.RawID.Value);

                int eBase = dataBase + offsets[i];
                switch (elem.Type)
                {
                    case TrafficElementType.SpeedLimit:
                        {
                            var speedLimit = elem as SpeedLimit;
                            data[eBase] = new GeneralSampleValue((double)speedLimit.LimitedSpeed);
                            if (speedLimit.PosX != null) data[eBase + 1] = new GeneralSampleValue(speedLimit.PosX.Value);
                            if (speedLimit.PosY != null) data[eBase + 2] = new GeneralSampleValue(speedLimit.PosY.Value);
                            if (speedLimit.PosZ != null) data[eBase + 3] = new GeneralSampleValue(speedLimit.PosZ.Value);
                        }
                        break;
                    case TrafficElementType.TrafficSign:
                        {
                            var trafficSign = elem as TrafficSign;
                            if (trafficSign.PosX != null) data[eBase] = new GeneralSampleValue(trafficSign.PosX.Value);
                            if (trafficSign.PosY != null) data[eBase + 1] = new GeneralSampleValue(trafficSign.PosY.Value);
                            if (trafficSign.PosZ != null) data[eBase + 2] = new GeneralSampleValue(trafficSign.PosZ.Value);
                            data[eBase + 3] = new GeneralSampleValue((double)trafficSign.SignType);
                        }
                        break;
                    case TrafficElementType.TrafficLight:
                        {
                            var trafficLight = elem as TrafficLight;
                            data[eBase] = new GeneralSampleValue((double)trafficLight.State);
                            if (trafficLight.PosX != null) data[eBase + 1] = new GeneralSampleValue(trafficLight.PosX.Value);
                            if (trafficLight.PosY != null) data[eBase + 2] = new GeneralSampleValue(trafficLight.PosY.Value);
                            if (trafficLight.PosZ != null) data[eBase + 3] = new GeneralSampleValue(trafficLight.PosZ.Value);
                            if (trafficLight.Arrows != null)
                            {
                                data[eBase + 4] = new GeneralSampleValue(trafficLight.Arrows.GoStraight ? 1 : 0);
                                data[eBase + 5] = new GeneralSampleValue(trafficLight.Arrows.TurnLeft ? 1 : 0);
                                data[eBase + 6] = new GeneralSampleValue(trafficLight.Arrows.TurnRight ? 1 : 0);
                                data[eBase + 7] = new GeneralSampleValue(trafficLight.Arrows.UTurn ? 1 : 0);
                            }
                            if (trafficLight.StopPosX != null) data[eBase + 8] = new GeneralSampleValue(trafficLight.StopPosX.Value);
                            if (trafficLight.StopPosY != null) data[eBase + 9] = new GeneralSampleValue(trafficLight.StopPosY.Value);
                        }
                        break;
                    case TrafficElementType.StopLine:
                        {
                            var stopLine = elem as StopLine;
                            data[eBase] = new GeneralSampleValue(stopLine.Distance);
                            if (stopLine.Heading != null) data[eBase + 1] = new GeneralSampleValue(stopLine.Heading.Value);
                        }
                        break;
                    case TrafficElementType.SpeedBump:
                        {
                            var speedBump = elem as SpeedBump;
                            data[eBase] = new GeneralSampleValue(speedBump.Distance);
                            if (speedBump.Heading != null) data[eBase + 1] = new GeneralSampleValue(speedBump.Heading.Value);
                        }
                        break;
                    case TrafficElementType.ArrowMark:
                        {
                            var arrowMark = elem as ArrowMark;
                            data[eBase] = new GeneralSampleValue(arrowMark.Arrows.GoStraight ? 1 : 0);
                            data[eBase + 1] = new GeneralSampleValue(arrowMark.Arrows.TurnLeft ? 1 : 0);
                            data[eBase + 2] = new GeneralSampleValue(arrowMark.Arrows.TurnRight ? 1 : 0);
                            data[eBase + 3] = new GeneralSampleValue(arrowMark.Arrows.UTurn ? 1 : 0);
                            if (arrowMark.PosX != null) data[eBase + 4] = new GeneralSampleValue(arrowMark.PosX.Value);
                            if (arrowMark.PosY != null) data[eBase + 5] = new GeneralSampleValue(arrowMark.PosY.Value);
                        }
                        break;
                    case TrafficElementType.ProhibitionZone:
                        {
                            var prohibitionZone = elem as ProhibitionZone;
                            data[eBase] = new GeneralSampleValue(prohibitionZone.CenterX);
                            data[eBase + 1] = new GeneralSampleValue(prohibitionZone.CenterY);
                            data[eBase + 2] = new GeneralSampleValue(prohibitionZone.Width);
                            data[eBase + 3] = new GeneralSampleValue(prohibitionZone.Length);
                            if (prohibitionZone.Heading != null) data[eBase + 4] = new GeneralSampleValue(prohibitionZone.Heading.Value);
                        }
                        break;
                    case TrafficElementType.ParkingSlot:
                        {
                            var parkingSlot = elem as ParkingSlot;
                            data[eBase] = new GeneralSampleValue(parkingSlot.CenterX);
                            data[eBase + 1] = new GeneralSampleValue(parkingSlot.CenterY);
                            data[eBase + 2] = new GeneralSampleValue(parkingSlot.Width);
                            data[eBase + 3] = new GeneralSampleValue(parkingSlot.Length);
                            if (parkingSlot.Heading != null) data[eBase + 4] = new GeneralSampleValue(parkingSlot.Heading.Value);
                            data[eBase + 5] = new GeneralSampleValue((double)parkingSlot.Occupancy);
                        }
                        break;
                    case TrafficElementType.ZebraCrossing:
                        {
                            var zebraCrossing = elem as ZebraCrossing;
                            data[eBase] = new GeneralSampleValue(zebraCrossing.NearDistance);
                            if (zebraCrossing.FarDistance != null) data[eBase + 1] = new GeneralSampleValue(zebraCrossing.FarDistance.Value);
                            if (zebraCrossing.Heading != null) data[eBase + 2] = new GeneralSampleValue(zebraCrossing.Heading.Value);
                        }
                        break;
                }
            }

            sample.Values = data.ToList();
            sample.NumberOfSignificants = data.Length;

            return sample;
        }

        public override bool FromGeneralSample(GeneralSample sample)
        {
            SetTime(sample);

            #region traffic-sample-v2
            if (sample.Protocol == "traffic-sample-v2")
            {
                var v = sample.Values.ToArray();
                if (v.Length < 1) return false;
                if (!v[0].IsNumber()) return false;

                int count = v[0].ToInt().Value;
                if (v.Length < 1 + 6 * count) return false;

                if (count > 0)
                {
                    int lastOffset = v[1 + 6 * (count - 1)].ToInt().Value;
                    int lastLength = v[2 + 6 * (count - 1)].ToInt().Value;
                    int totalDataSize = lastOffset + lastLength;
                    if (v.Length != 1 + 6 * count + totalDataSize) return false;
                }

                var list = new List<TrafficElement>();
                for (int i = 0; i < count; i++)
                {
                    if (!v[1 + 6 * i].IsNumber() || !v[2 + 6 * i].IsNumber() || !v[3 + 6 * i].IsNumber() || !v[4 + 6 * i].IsNumber()) continue;

                    var type = (TrafficElementType)v[3 + 6 * i].ToInt().Value;
                    if (type == TrafficElementType.Invalid) continue;

                    int bas = 1 + 6 * count + v[1 + 6 * i].ToInt().Value;
                    int len = v[2 + 6 * i].ToInt().Value;
                    int id = v[4 + 6 * i].ToInt().Value;
                    int? rawID = v[5 + 6 * i].ToInt();

                    TrafficElement newElem = null;
                    switch (type)
                    {
                        case TrafficElementType.SpeedLimit:
                            if (len != 6) break;
                            newElem = new SpeedLimit()
                            {
                                ID = id,
                                RawID = rawID,
                                LimitedSpeed = (uint)v[bas].ToInt().Value,
                                PosX = v[bas + 1].ToDouble(),
                                PosY = v[bas + 2].ToDouble(),
                                PosZ = v[bas + 3].ToDouble(),
                            };
                            break;
                        case TrafficElementType.TrafficSign:
                            if (len != 5) break;
                            newElem = new TrafficSign()
                            {
                                ID = id,
                                RawID = rawID,
                                SignType = v[bas + 3].ToInt() == null ? TrafficSignType.Stop : (TrafficSignType)v[bas + 3].ToInt().Value,
                                PosX = v[bas].ToDouble(),
                                PosY = v[bas + 1].ToDouble(),
                                PosZ = v[bas + 2].ToDouble(),
                            };
                            break;
                        case TrafficElementType.TrafficLight:
                            if (len != 10) break;
                            var trafficLight = new TrafficLight()
                            {
                                ID = id,
                                RawID = rawID,
                                State = (TrafficLightState)v[bas].ToInt().Value,
                                PosX = v[bas + 1].ToDouble(),
                                PosY = v[bas + 2].ToDouble(),
                                PosZ = v[bas + 3].ToDouble(),
                                StopPosX = v[bas + 8].ToDouble(),
                                StopPosY = v[bas + 9].ToDouble(),
                            };
                            if (v[bas + 4].IsNumber() && v[bas + 5].IsNumber() && v[bas + 6].IsNumber() && v[bas + 7].IsNumber())
                            {
                                trafficLight.Arrows = new ArrowCombination()
                                {
                                    GoStraight = v[bas + 4].ToInt().Value == 1,
                                    TurnLeft = v[bas + 5].ToInt().Value == 1,
                                    TurnRight = v[bas + 6].ToInt().Value == 1,
                                    UTurn = v[bas + 7].ToInt().Value == 1,
                                };
                            }
                            newElem = trafficLight;
                            break;
                        case TrafficElementType.StopLine:
                            if (len != 4) break;
                            newElem = new StopLine()
                            {
                                ID = id,
                                RawID = rawID,
                                Distance = v[bas].ToDouble().Value,
                                Heading = v[bas + 1].ToDouble(),
                            };
                            break;
                        case TrafficElementType.SpeedBump:
                            if (len != 4) break;
                            newElem = new SpeedBump()
                            {
                                ID = id,
                                RawID = rawID,
                                Distance = v[bas].ToDouble().Value,
                                Heading = v[bas + 1].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ArrowMark:
                            if (len != 8) break;
                            newElem = new ArrowMark()
                            {
                                ID = id,
                                RawID = rawID,
                                Arrows = new ArrowCombination()
                                {
                                    GoStraight = v[bas].ToInt().Value == 1,
                                    TurnLeft = v[bas + 1].ToInt().Value == 1,
                                    TurnRight = v[bas + 2].ToInt().Value == 1,
                                    UTurn = v[bas + 3].ToInt().Value == 1,
                                },
                                PosX = v[bas + 4].ToDouble(),
                                PosY = v[bas + 5].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ProhibitionZone:
                            if (len != 7) break;
                            newElem = new ProhibitionZone()
                            {
                                ID = id,
                                RawID = rawID,
                                CenterX = v[bas].ToDouble().Value,
                                CenterY = v[bas + 1].ToDouble().Value,
                                Width = v[bas + 2].ToDouble().Value,
                                Length = v[bas + 3].ToDouble().Value,
                                Heading = v[bas + 4].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ParkingSlot:
                            if (len != 7) break;
                            newElem = new ParkingSlot()
                            {
                                ID = id,
                                RawID = rawID,
                                CenterX = v[bas].ToDouble().Value,
                                CenterY = v[bas + 1].ToDouble().Value,
                                Width = v[bas + 2].ToDouble().Value,
                                Length = v[bas + 3].ToDouble().Value,
                                Heading = v[bas + 4].ToDouble(),
                                Occupancy = v[bas + 5].IsNumber() ? (ParkingSlotOccupancy)v[bas + 5].ToInt().Value : ParkingSlotOccupancy.Unknown,
                            };
                            break;
                        case TrafficElementType.ZebraCrossing:
                            if (len != 5) break;
                            newElem = new ZebraCrossing()
                            {
                                ID = id,
                                RawID = rawID,
                                NearDistance = v[bas].ToDouble().Value,
                                FarDistance = v[bas + 1].ToDouble(),
                                Heading = v[bas + 2].ToDouble(),
                            };
                            break;
                    }
                    if (newElem != null) list.Add(newElem);
                }
                Elements = list;

                return true;
            }
            #endregion

            #region traffic-sample-v1
            else if (sample.Protocol == "traffic-sample-v1")
            {
                var v = sample.Values.ToArray();
                if (v.Length < 1) return false;
                if (!v[0].IsNumber()) return false;

                int count = v[0].ToInt().Value;
                if (v.Length < 1 + 3 * count) return false;

                if (count > 0)
                {
                    int lastOffset = v[3 * count - 1].ToInt().Value;
                    int lastLength = v[3 * count].ToInt().Value;
                    int totalDataSize = lastOffset + lastLength;
                    if (v.Length < 1 + 3 * count + totalDataSize) return false;
                }

                var list = new List<TrafficElement>();
                for (int i = 0; i < count; i++)
                {
                    if (!v[1 + 3 * i].IsNumber() || !v[2 + 3 * i].IsNumber()) continue;

                    var type = (TrafficElementType)v[1 + 3 * i].ToInt().Value;
                    if (type == TrafficElementType.Invalid) continue;

                    int bas = 1 + 3 * count + v[2 + 3 * i].ToInt().Value;

                    TrafficElement newElem = null;
                    switch (type)
                    {
                        case TrafficElementType.SpeedLimit:
                            newElem = new SpeedLimit()
                            {
                                LimitedSpeed = (uint)v[bas].ToInt().Value,
                                PosX = v[bas + 1].ToDouble(),
                                PosY = v[bas + 2].ToDouble(),
                            };
                            break;
                        case TrafficElementType.TrafficSign:
                            newElem = new TrafficSign()
                            {
                                SignType = TrafficSignType.Stop,
                                PosX = v[bas].ToDouble(),
                                PosY = v[bas + 1].ToDouble(),
                            };
                            break;
                        case TrafficElementType.TrafficLight:
                            var trafficLight = new TrafficLight()
                            {
                                State = (TrafficLightState)v[bas].ToInt().Value,
                                PosX = v[bas + 1].ToDouble(),
                                PosY = v[bas + 2].ToDouble(),
                            };
                            if (v[bas + 3].IsNumber() && v[bas + 4].IsNumber() && v[bas + 5].IsNumber() && v[bas + 6].IsNumber())
                            {
                                trafficLight.Arrows = new ArrowCombination()
                                {
                                    GoStraight = v[bas + 3].ToInt().Value == 1,
                                    TurnLeft = v[bas + 4].ToInt().Value == 1,
                                    TurnRight = v[bas + 5].ToInt().Value == 1,
                                    UTurn = v[bas + 6].ToInt().Value == 1,
                                };
                            }
                            newElem = trafficLight;
                            break;
                        case TrafficElementType.StopLine:
                            newElem = new StopLine()
                            {
                                Distance = v[bas].ToDouble().Value,
                                Heading = v[bas + 1].ToDouble(),
                            };
                            break;
                        case TrafficElementType.SpeedBump:
                            newElem = new SpeedBump()
                            {
                                Distance = v[bas].ToDouble().Value,
                                Heading = v[bas + 1].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ArrowMark:
                            newElem = new ArrowMark()
                            {
                                Arrows = new ArrowCombination()
                                {
                                    GoStraight = v[bas].ToInt().Value == 1,
                                    TurnLeft = v[bas + 1].ToInt().Value == 1,
                                    TurnRight = v[bas + 2].ToInt().Value == 1,
                                    UTurn = v[bas + 3].ToInt().Value == 1,
                                },
                                PosX = v[bas + 4].ToDouble(),
                                PosY = v[bas + 5].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ProhibitionZone:
                            newElem = new ProhibitionZone()
                            {
                                CenterX = v[bas].ToDouble().Value,
                                CenterY = v[bas + 1].ToDouble().Value,
                                Width = v[bas + 2].ToDouble().Value,
                                Length = v[bas + 3].ToDouble().Value,
                                Heading = v[bas + 4].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ParkingSlot:
                            newElem = new ParkingSlot()
                            {
                                CenterX = v[bas].ToDouble().Value,
                                CenterY = v[bas + 1].ToDouble().Value,
                                Width = v[bas + 2].ToDouble().Value,
                                Length = v[bas + 3].ToDouble().Value,
                                Heading = v[bas + 4].ToDouble(),
                            };
                            break;
                        case TrafficElementType.ZebraCrossing:
                            newElem = new ZebraCrossing()
                            {
                                NearDistance = v[bas].ToDouble().Value,
                                FarDistance = v[bas + 1].ToDouble(),
                                Heading = v[bas + 2].ToDouble(),
                            };
                            break;
                    }
                    if (newElem != null) list.Add(newElem);
                }
                Elements = list;

                return true;
            }
            #endregion

            return false;
        }

        public override bool SupportInterpolation()
        {
            return true;
        }

        protected override Sample Interpolate(SearchResult input)
        {
            var s1 = input.s1 as TrafficSample;
            var s2 = input.s2 as TrafficSample;
            var w1 = input.w1;
            var w2 = input.w2;

            bool noID = false;
            foreach (var elem in s1.Elements)
            {
                if (elem.ID <= 0)
                {
                    noID = true;
                    break;
                }
            }
            foreach (var elem in s2.Elements)
            {
                if (elem.ID <= 0)
                {
                    noID = true;
                    break;
                }
            }

            if (noID)
            {
                return w1 > w2 ? s1 : s2;
            }

            var output = new TrafficSample();

            var elems = new List<TrafficElement>();
            for (int i = 0; i < s1.Elements.Count; i++)
            {
                var s1ElemB = s1.Elements[i];
                for (int j = 0; j < s2.Elements.Count; j++)
                {
                    var s2ElemB = s2.Elements[j];

                    if (s1ElemB.ID == s2ElemB.ID && s1ElemB.Type == s2ElemB.Type)
                    {
                        TrafficElement elem = null;
                        switch (s1ElemB.Type)
                        {
                            case TrafficElementType.SpeedLimit:
                                {
                                    var s1Elem = s1ElemB as SpeedLimit;
                                    var s2Elem = s2ElemB as SpeedLimit;
                                    elem = new SpeedLimit()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        LimitedSpeed = w1 > w2 ? s1Elem.LimitedSpeed : s2Elem.LimitedSpeed,
                                        PosX = s1Elem.PosX * w1 + s2Elem.PosX * w2,
                                        PosY = s1Elem.PosY * w1 + s2Elem.PosY * w2,
                                        PosZ = s1Elem.PosZ * w1 + s2Elem.PosZ * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.TrafficSign:
                                {
                                    var s1Elem = s1ElemB as TrafficSign;
                                    var s2Elem = s2ElemB as TrafficSign;
                                    elem = new TrafficSign()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        SignType = w1 > w2 ? s1Elem.SignType : s2Elem.SignType,
                                        PosX = s1Elem.PosX * w1 + s2Elem.PosX * w2,
                                        PosY = s1Elem.PosY * w1 + s2Elem.PosY * w2,
                                        PosZ = s1Elem.PosZ * w1 + s2Elem.PosZ * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.TrafficLight:
                                {
                                    var s1Elem = s1ElemB as TrafficLight;
                                    var s2Elem = s2ElemB as TrafficLight;
                                    elem = new TrafficLight()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        State = w1 > w2 ? s1Elem.State : s2Elem.State,
                                        Arrows = w1 > w2 ? s1Elem.Arrows : s2Elem.Arrows,
                                        PosX = s1Elem.PosX * w1 + s2Elem.PosX * w2,
                                        PosY = s1Elem.PosY * w1 + s2Elem.PosY * w2,
                                        PosZ = s1Elem.PosZ * w1 + s2Elem.PosZ * w2,
                                        StopPosX = s1Elem.StopPosX * w1 + s2Elem.StopPosX * w2,
                                        StopPosY = s1Elem.StopPosY * w1 + s2Elem.StopPosY * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.StopLine:
                                {
                                    var s1Elem = s1ElemB as StopLine;
                                    var s2Elem = s2ElemB as StopLine;
                                    elem = new StopLine()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        Distance = s1Elem.Distance * w1 + s2Elem.Distance * w2,
                                        Heading = s1Elem.Heading * w1 + s2Elem.Heading * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.SpeedBump:
                                {
                                    var s1Elem = s1ElemB as SpeedBump;
                                    var s2Elem = s2ElemB as SpeedBump;
                                    elem = new SpeedBump()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        Distance = s1Elem.Distance * w1 + s2Elem.Distance * w2,
                                        Heading = s1Elem.Heading * w1 + s2Elem.Heading * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.ArrowMark:
                                {
                                    var s1Elem = s1ElemB as ArrowMark;
                                    var s2Elem = s2ElemB as ArrowMark;
                                    elem = new ArrowMark()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        Arrows = w1 > w2 ? s1Elem.Arrows : s2Elem.Arrows,
                                        PosX = s1Elem.PosX * w1 + s2Elem.PosX * w2,
                                        PosY = s1Elem.PosY * w1 + s2Elem.PosY * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.ProhibitionZone:
                                {
                                    var s1Elem = s1ElemB as ProhibitionZone;
                                    var s2Elem = s2ElemB as ProhibitionZone;
                                    elem = new ProhibitionZone()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        CenterX = s1Elem.CenterX * w1 + s2Elem.CenterX * w2,
                                        CenterY = s1Elem.CenterY * w1 + s2Elem.CenterY * w2,
                                        Width = s1Elem.Width * w1 + s2Elem.Width * w2,
                                        Length = s1Elem.Length * w1 + s2Elem.Length * w2,
                                        Heading = s1Elem.Heading * w1 + s2Elem.Heading * w2,
                                    };
                                }
                                break;
                            case TrafficElementType.ParkingSlot:
                                {
                                    var s1Elem = s1ElemB as ParkingSlot;
                                    var s2Elem = s2ElemB as ParkingSlot;
                                    elem = new ParkingSlot()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        CenterX = s1Elem.CenterX * w1 + s2Elem.CenterX * w2,
                                        CenterY = s1Elem.CenterY * w1 + s2Elem.CenterY * w2,
                                        Width = s1Elem.Width * w1 + s2Elem.Width * w2,
                                        Length = s1Elem.Length * w1 + s2Elem.Length * w2,
                                        Heading = s1Elem.Heading * w1 + s2Elem.Heading * w2,
                                        Occupancy = w1 > w2 ? s1Elem.Occupancy : s2Elem.Occupancy,
                                    };
                                }
                                break;
                            case TrafficElementType.ZebraCrossing:
                                {
                                    var s1Elem = s1ElemB as ZebraCrossing;
                                    var s2Elem = s2ElemB as ZebraCrossing;
                                    elem = new ZebraCrossing()
                                    {
                                        ID = s1Elem.ID,
                                        RawID = w1 > w2 ? s1Elem.RawID : s2Elem.RawID,
                                        NearDistance = s1Elem.NearDistance * w1 + s2Elem.NearDistance * w2,
                                        FarDistance = s1Elem.FarDistance * w1 + s2Elem.FarDistance * w2,
                                        Heading = s1Elem.Heading * w1 + s2Elem.Heading * w2,
                                    };
                                }
                                break;
                        }

                        if (elem != null) elems.Add(elem);
                        break;
                    }
                }
            }
            output.Elements = elems;

            return output;
        }
    }
}
